use std::collections::HashMap;

use crate::{
    comptime::{self, Parser}, runtime::{self, env::Error, Value}
};

pub trait Visitor {
    fn visit(&mut self, cur: &Expr, compound_idx: Option<usize>);
}

impl<F: FnMut(&Expr, Option<usize>)> Visitor for F {
    fn visit(&mut self, cur: &Expr, compound_idx: Option<usize>) {
        self(cur, compound_idx)
    }
}

// Expr

macro_rules! impl_expr_is {
    ($method: ident: $tag: pat) => {
        pub fn $method(&self) -> bool {
            if let $tag = self {
                true
            } else {
                false
            }
        }
    };
    ($method: ident: $tag: pat => $ret: expr) => {
        pub fn $method(&self) -> bool {
            if let $tag = self {
                $ret
            } else {
                false
            }
        }
    };
    ($method: ident ($($p: ident : $pty: ty),+): $tag: pat => $ret: expr) => {
        pub fn $method(&self,$($p : $pty),+) -> bool {
            if let $tag = self {
                $ret
            } else {
                false
            }
        }
    };
}

macro_rules! impl_expr_as {
    ($method: ident -> $target: ty: $tag: pat => $ret: expr) => {
        pub fn $method(&self) -> Option<$target> {
            if let $tag = self {
                Some($ret)
            } else {
                None
            }
        }
    };
}

macro_rules! impl_expr_into {
    ($method: ident -> $target: ty: $tag: pat => $ret: expr) => {
        pub fn $method(self) -> Option<$target> {
            if let $tag = self {
                Some($ret)
            } else {
                None
            }
        }
    };
}

macro_rules! impl_expr_into_literal {
    ($method: ident: $lit_method: path => $target: ty) => {
        pub fn $method(self) -> Option<$target> {
            self.into_literal().and_then($lit_method)
        }
    };
}

#[derive(Debug, Clone)]
pub enum Expr {
    Nil,
    Compound(Vec<Expr>),
    List(Vec<Expr>),
    Block(Vec<Expr>),
    Ident(String),
    Literal(Literal),
    Quoted(Box<Expr>),
    Operation(Operator, Vec<Expr>),
    Proto(FuncProto),
    Fn(String, FuncProto),
    Def(String, Box<Expr>),
    Let(Box<Expr>, Box<Expr>),
}

impl Expr {
    impl_expr_is!(is_nil: Expr::Nil);
    impl_expr_is!(is_literal: Expr::Literal(_));
    impl_expr_is!(is_compound: Expr::Compound(_));
    impl_expr_is!(is_list: Expr::List(_));
    impl_expr_is!(is_ident(name: &str): Expr::Ident(id) => id == name);
    impl_expr_is!(is_compound_pair: Expr::Compound(exprs) => exprs.len() == 2);
    impl_expr_is!(is_string_literal(s: &str): Self::Literal(Literal::String(ls)) => s == ls);

    impl_expr_as!(as_ident -> &str: Expr::Ident(v) => v.as_str());
    impl_expr_as!(as_literal -> &Literal: Expr::Literal(v) => v);
    impl_expr_as!(as_compound -> &Vec<Expr>: Expr::Compound(v) => v);

    impl_expr_into!(into_ident -> String: Expr::Ident(id) => id);
    impl_expr_into!(into_literal -> Literal: Expr::Literal(l) => l);
    impl_expr_into!(into_compound -> Vec<Expr>: Expr::Compound(exprs) => exprs);
    impl_expr_into!(into_list -> Vec<Expr>: Expr::List(exprs) => exprs);

    impl_expr_into_literal!(into_bool_literal: Literal::into_bool => bool);
    impl_expr_into_literal!(into_i32_literal: Literal::into_i32 => i32);
    impl_expr_into_literal!(into_f32_literal: Literal::into_f32 => f32);
    impl_expr_into_literal!(into_string_literal: Literal::into_string => String);

    // (operator ret)
    pub fn into_combination(self, operator: &str) -> Option<Vec<Expr>> {
        if let Self::Compound(mut exprs) = self {
            if exprs
                .pop()
                .and_then(Expr::into_ident)
                .is_some_and(|id| id == operator)
            {
                return Some(exprs);
            }
        }
        None
    }

    pub fn into_compound_iter(self) -> Option<impl Iterator<Item = Expr>> {
        self.into_compound().map(Vec::into_iter).map(Iterator::rev)
    }

    // (a: any b: any)
    pub fn into_compound_pair(self) -> Option<(Expr, Expr)> {
        if let Self::Compound(exprs) = &self {
            if exprs.len() == 2 {
                return self
                    .into_compound_iter()
                    .map(|it| it.take(2))
                    .and_then(|mut it| it.next().zip(it.next()));
            }
        }
        None
    }

    pub fn is_kv_pair(&self, key: &str) -> bool {
        if let Some(exprs) = self.as_compound() {
            exprs.last().is_some_and(|l| l.is_ident(key))
        } else {
            false
        }
    }

    // (k: ident v: any)
    pub fn into_kv_pair(self) -> Option<(String, Expr)> {
        self.into_compound_pair()
            .and_then(|(id, expr)| Some((id.into_ident()?, expr)))
    }

    // KV Iter: ((k1 v1) (k2 v1) ... )
    pub fn into_kv_iter(self) -> Option<impl Iterator<Item = (String, Expr)>> {
        self.into_compound_iter()
            .map(|it| it.flat_map(Expr::into_kv_pair))
    }

    pub fn into_table(self) -> Option<HashMap<String, Expr>> {
        self.into_kv_iter().map(Iterator::collect)
    }

    pub fn visit<V: Visitor>(&self, mut visitor: V) {
        self.visit_by(&mut visitor);
    }

    pub fn visit_by<V: Visitor>(&self, visitor: &mut V) {
        visitor.visit(self, None);
        if let Expr::Compound(exprs) = self {
            for (idx, expr) in exprs.iter().enumerate() {
                expr.visit_component(visitor, idx)
            }
        }
    }

    fn visit_component<V: Visitor>(&self, visitor: &mut V, index: usize) {
        visitor.visit(self, Some(index));
        if let Expr::Compound(exprs) = self {
            for (idx, expr) in exprs.iter().enumerate() {
                expr.visit_component(visitor, idx)
            }
        }
    }
}

impl std::fmt::Display for Expr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Expr::Nil => write!(f, "()"),
            Expr::Compound(exprs) => write!(
                f,
                "({})",
                exprs
                    .iter()
                    .rev()
                    .map(Expr::to_string)
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            Expr::List(exprs) => write!(
                f,
                "[{}]",
                exprs
                    .iter()
                    .map(Expr::to_string)
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            Expr::Block(exprs) => write!(
                f,
                "(begin {})",
                exprs
                    .iter()
                    .rev()
                    .map(Expr::to_string)
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            Expr::Ident(id) => write!(f, "{id}"),
            Expr::Literal(lit) => write!(f, "{}", Into::<Value>::into(lit.clone())),
            Expr::Proto(FuncProto { params, body }) => write!(f, "|{}|{}", params.join(" "), &body),
            Expr::Operation(op, exprs) => write!(
                f,
                "({:?} {})",
                op,
                exprs
                    .iter()
                    .rev()
                    .map(Expr::to_string)
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            Expr::Quoted(q) => write!(f, "{}", q),
            Expr::Def(id, val) => write!(f, "(def {id} {val})"),
            Expr::Let(vars, body) => write!(f, "(let {vars} {body})"),
            Expr::Fn(_, _) => todo!(),
        }
    }
}

impl TryFrom<&str> for Expr {
    type Error = comptime::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Parser::new(value).parse_single_expr()
    }
}

// Literal

macro_rules! impl_literal_from {
    ($src: ty, $tag: path) => {
        impl From<$src> for Literal {
            fn from(value: $src) -> Self {
                $tag(value)
            }
        }
    };
}

macro_rules! impl_literal_into_conv {
    ($name: ident : $tag: path => $out: ty) => {
        pub fn $name(self) -> Option<$out> {
            if let $tag(v) = self {
                Some(v)
            } else {
                None
            }
        }
    };
}

macro_rules! impl_literal_as {
    ($name: ident : $tag: path as $out: ty) => {
        pub fn $name(&self) -> Option<&$out> {
            if let $tag(v) = self {
                Some(v)
            } else {
                None
            }
        }
    };
}

#[derive(Debug, Clone)]
pub enum Literal {
    Boolean(bool),
    Integer(i32),
    Real(f32),
    String(String),
}

impl Literal {
    impl_literal_into_conv!(into_string: Self::String => String);
    impl_literal_into_conv!(into_i32: Self::Integer => i32);
    impl_literal_into_conv!(into_f32: Self::Real => f32);
    impl_literal_into_conv!(into_bool: Self::Boolean => bool);

    impl_literal_as!(as_bool: Self::Boolean as bool);
    impl_literal_as!(as_i32: Self::Integer as i32);
    impl_literal_as!(as_f32: Self::Real as f32);
    impl_literal_as!(as_string: Self::String as String);
}

impl_literal_from!(bool, Literal::Boolean);
impl_literal_from!(i32, Literal::Integer);
impl_literal_from!(f32, Literal::Real);
impl_literal_from!(String, Literal::String);

impl TryFrom<Expr> for Literal {
    type Error = runtime::Error;

    fn try_from(value: Expr) -> Result<Self, Self::Error> {
        match value {
            Expr::Literal(lit) => Ok(lit),
            _ => Err(Error::UnsupportedConvert)?,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub enum Operator {
    Add,
    Sub,
    Mul,
    Div,
    Shl,
    Shr,
    BitAnd,
    BitOr,
    BitNot,
    If,
    And,
    Or,
    Not,
    Eq,
    LessThan,
    GreaterThan,
    LessEq,
    GreaterEq,
    At,
    Cons,
    Car,
    Cdr,
    Err,
    Eval,
}

impl TryFrom<&str> for Operator {
    type Error = ();

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "+" => Ok(Self::Add),
            "-" => Ok(Self::Sub),
            "*" => Ok(Self::Mul),
            "/" => Ok(Self::Div),
            "@" => Ok(Self::At),
            "<<" => Ok(Self::Shl),
            ">>" => Ok(Self::Shr),
            "&" => Ok(Self::And),
            "|" => Ok(Self::Or),
            "~" => Ok(Self::Not),
            "if" => Ok(Self::If),
            "and" => Ok(Self::And),
            "or" => Ok(Self::Or),
            "not" => Ok(Self::Not),
            "=" => Ok(Self::Eq),
            "<" => Ok(Self::LessThan),
            ">" => Ok(Self::GreaterThan),
            "<=" => Ok(Self::LessEq),
            ">=" => Ok(Self::GreaterEq),
            "cons" => Ok(Self::Cons),
            "car" => Ok(Self::Car),
            "cdr" => Ok(Self::Cdr),
            "err" => Ok(Self::Err),
            "eval" => Ok(Self::Eval),
            _ => Err(()),
        }
    }
}

#[derive(Debug, Clone)]
pub struct FuncProto {
    pub params: Vec<String>,
    pub body: Box<Expr>,
}
